package org.apache.ibatis.common.io;

import org.apache.ibatis.common.logging.Log;
import org.apache.ibatis.common.logging.LogFactory;
import sun.security.x509.AVA;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @Name VFS
 * @Description: 虚拟文件系统 [抽象] 类
 * 描述信息：从应用服务器中找寻和读取资源文件（配置文件、类文件等）。
 * VFS（Virtual File System）作为一个虚拟的文件系统将各个磁盘文件系统（FAT、VFAT、NTFS、NFS等）的差异屏蔽起来，提供了统一的操作接口。这使得上层的软件能够用统一的方式来跟底层不同的文件系统进行交互。
 * 即：在操作磁盘文件时，软件程序不需要和实体的文件系统打交道，只需要和 VFS 沟通即可。这使得软件系统的磁盘操作变得简单。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-14 16:36:05
 **/
public abstract class VFS {
    private static final Log log = LogFactory.getLog(VFS.class);
    public static final Class<?>[] IMPLEMENTATIONS = {};
    public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();

    private static class VFSHolder {
        static final VFS INSTANCE = createVFS();

        /**
         * 创建 VFS 实现类 [单例模式]
         */
        static VFS createVFS() {
            // 所有 VFS 实现类列表
            List<Class<? extends VFS>> impls = new ArrayList<>();
            // 首先加入用户自定义的实现类，优先级最高
            impls.addAll(USER_IMPLEMENTATIONS);
            impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
            VFS vfs = null;
            // 依次生成 VFS 实例，并找出第一个可用的实例
            for (int i = 0; vfs == null || !vfs.isValid(); i++) {
                Class<? extends VFS> impl = impls.get(i);
                try {
                    vfs = impl.newInstance();
                    if (vfs == null || !vfs.isValid()) {
                        if (log.isDebugEnabled()) {
                            String msg = "VFS 实现类 [" + impl.getName() + "] 在当前环境中是无效的。";
                            log.debug(msg);
                        }
                    }
                } catch (InstantiationException | IllegalAccessException e) {
                    String msg = "无法完成实例化：" + impl;
                    log.error(msg, e);
                    return null;
                }
            }
            if (log.isDebugEnabled()) {
                String msg = "正在使用 VFS 适配器：" + vfs.getClass().getName();
                log.debug(msg);
            }
            return vfs;
        }
    }

    public abstract boolean isValid();

    public static VFS getInstance() {
        return VFSHolder.INSTANCE;
    }

    public static void addImplClass(Class<? extends VFS> clazz) {
        if (clazz != null) USER_IMPLEMENTATIONS.add(clazz);
    }

    protected static Class<?> getClass(String className) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            if (log.isDebugEnabled()) {
                String msg = "目标类 [" + className + "] 不存在，原因：" + e;
                log.debug(msg);
            }
            return null;
        }
    }

    protected static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        if (clazz == null) return null;
        try {
            return clazz.getMethod(methodName, parameterTypes);
        } catch (SecurityException e) {
            String msg = "在寻找方法 [" + clazz.getName() + "." + methodName + "] 时遇到了安全异常，异常原因：" + e;
            log.error(msg);
            return null;
        } catch (NoSuchMethodException e) {
            String msg = "目标方法 [" + clazz.getName() + "." + methodName + "] 不存在，原因：" + e;
            log.error(msg);
            return null;
        }
    }

    protected static <T> T invoke(Method method, Object object, Object... parameters) throws IOException, RuntimeException {
        try {
            return (T) method.invoke(object, parameters);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof IOException) {
                throw (IOException) e.getTargetException();
            } else throw new RuntimeException(e);
        }
    }

    public List<String> list(String path) throws IOException {
        List<String> names = new ArrayList<>();
        for (URL url : getResources(path)) {
            names.addAll(list(url, path));
        }
        return names;
    }

    protected static List<URL> getResources(String path) throws IOException {
        return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
    }

    protected abstract List<String> list(URL url, String path) throws IOException;

}

